using System;

namespace ChronoNES
{
    class Memory
    {
        byte[] wRAM;    // work ram
        byte[] sRAM;
        PPU ppu;
        Mapper mapper;

        public Memory(Mapper _mapper)
        {
            wRAM = new byte[2 * 1024]; //  2 KB
            sRAM = new byte[8 * 1024]; //  8 KB
            mapper = _mapper;

            ppu = new PPU();
       }

        public byte Read8(ushort _address)
        {
            if (_address <= 0x1FFF)
                return wRAM[_address & 0x7FF];
            else if (_address >= 0x2000 && _address <= 0x3FFF)
                return ppu.Read((ushort)(_address & 0x7));
            else if (_address >= 0x6000 && _address <= 0x7FFF)
                return sRAM[_address & 0x5FFF];
            else if (_address >= 0x8000 && _address <= 0xFFFF)
                return mapper.Read((ushort)(_address & 0x7FFF));

            throw new Exception(String.Format("Unsupported 8bit read at memory address: 0x{0:X}", _address));
        }

        public ushort Read16(ushort _address)
        {
            if (_address <= 0x1FFF)
            {
                _address = (ushort)(_address & 0x7FF);
                return (ushort)((ushort)(wRAM[_address + 1] << 8) + (ushort)wRAM[_address]);
            }
            else if (_address >= 0x6000 && _address <= 0x7FFF)
                return (ushort)((ushort)(sRAM[(_address & 0x5FFF) + 1] << 8) + (ushort)sRAM[_address & 0x5FFF]);
            else if (_address >= 0x8000 && _address <= 0xFFFF)
                return mapper.Read16((ushort)(_address & 0x7FFF));

            throw new Exception(String.Format("Unsupported 16bit read at memory address: 0x{0:X}", _address));
        }

        public void write8(ushort _address, byte _value)
        {

            if (_address < 0x1FFF)
                wRAM[_address & 0x7FF] = _value;
            else if (_address >= 0x2000 && _address <= 0x3FFF)
                ppu.Write((ushort)(_address & 0x7), _value);
            else if (_address >= 0x6000 && _address <= 0x7FFF)
                sRAM[_address & 0x5FFF] = _value;
            else if (_address >= 0x8000 && _address <= 0xFFFF)
                mapper.Write((ushort)(_address & 0x7FFF), _value);
            else
                throw new Exception(String.Format("Unsupported 8bit write at memory address: 0x{0:X}", _address));
        }

        public void write16(ushort _address, ushort _value)
        {
            if (_address <= 0x1FFF)
            {
                _address = (ushort)(_address & 0x7FF);
                wRAM[_address + 1] = (byte)(_value >> 8);
                wRAM[_address] = (byte)(_value & 0xFF);
            }
            else if (_address >= 0x6000 && _address <= 0x7FFF)
            {
                sRAM[(_address & 0x5FFF) + 1] = (byte)(_value >> 8);
                sRAM[_address & 0x5FFF] = (byte)(_value & 0xFF);
            }
            else if (_address >= 0x8000 && _address <= 0xFFFF)
            {
                mapper.Write((ushort)(_address & 0x7FFF), (byte)(_value >> 8));
                mapper.Write((ushort)((_address & 0x7FFF) + 1), (byte)(_value & 0xFF));
            }
            else
                throw new Exception(String.Format("Unsupported 16bit write atmemory address: 0x{0:X}", _address));
        }
    }
}